#include "Sound.h"
#include "vorbisfile.h"
#include "Ogre.h"
#include "SoundManager.h"

namespace UniverseSound
{
	//////////////////////////////////////////////////////////////////////////////////////////
	// Functions to allow OGG Vorbis to stream audio data from an Ogre::DataStreamPtr
	size_t	StreamReading(void *ptr, size_t size, size_t nmemb, void *datasource)
	{
		Ogre::DataStreamPtr dataStream = *reinterpret_cast<Ogre::DataStreamPtr*>(datasource);
		return dataStream->read(ptr, size);
	}

	int		StreamSeeking(void *datasource, ogg_int64_t offset, int whence)
	{
		Ogre::DataStreamPtr dataStream = *reinterpret_cast<Ogre::DataStreamPtr*>(datasource);
		switch(whence)
		{
		case SEEK_SET:
			dataStream->seek(offset);
			break;
		case SEEK_END:
			dataStream->seek(dataStream->size());
			// Falling through purposefully here
		case SEEK_CUR:
			dataStream->skip(offset);
			break;
		}

		return 0;
	}

	int		StreamClosing(void *datasource)
	{
		return 0;
	}

	long	StreamTelling(void *datasource)
	{
		Ogre::DataStreamPtr dataStream = *reinterpret_cast<Ogre::DataStreamPtr*>(datasource);
		return static_cast<long>(dataStream->tell());
	}
	//////////////////////////////////////////////////////////////////////////////////////////

	SoundClip::SoundClip(const Ogre::String& name, bool seekSupport) : 
	mName(name),
	mSource(0),
	mLoop(false),
	mPlay(false),
	mPosition(0, 0, 0),
	mReferenceDistance(1.0f),
	mDirection(0, 0, 0),
	mVelocity(0, 0, 0),
	mGain(1.0f), 
	mMaxDistance(1E10), 
	mMaxGain(1.0f), 
	mMinGain(0.0f), 
	mPitch(1.0f), 
	mRolloffFactor(1.0f), 
	mInnerConeAngle(360.0f), 
	mOuterConeAngle(360.0f), 
	mOuterConeVolume(0.0f), 
	mPlayTime(0.0f), 
	mFadeTimer(0.0f), 
	mFadeTime(1.0f), 
	mFadeInitVol(0), 
	mFadeEndVol(1), 
	mFade(false),  
	mFadeEndAction(UniverseSound::FC_NONE),  
	mStream(false), 
	mFinCBEnabled(false), 
	mLoopCBEnabled(false), 
	mGiveUpSource(false),  
	mPlayPosChanged(false),  
	mPlayPos(0.f), 
	mPriority(0), 
	mFinishedCB(0), 
	mLoopCB(0), 
	mFileOpened(false),
	mSceneMgr(0),
	mLocalTransformDirty(true),
	mPlayDelayed(false),
	mStopDelayed(false),
	mPauseDelayed(false),
	mSeekable(true),
	mSourceRelative(false)
	{
		mOggCallbacks.read_func		= StreamReading;
		mOggCallbacks.close_func	= StreamClosing;
		mOggCallbacks.seek_func		= StreamSeeking;
		mOggCallbacks.tell_func		= StreamTelling;
	}

	SoundClip::~SoundClip()
	{
		mAudioStream.setNull();
	}

	void SoundClip::setPosition(float posx, float posy, float posz)
	{
		mPosition.x = posx;
		mPosition.y = posy;
		mPosition.z = posz;
		mLocalTransformDirty = true;
	}

	void SoundClip::setDirection(const Ogre::Vector3& dir)
	{
		mDirection = dir;
		mLocalTransformDirty = true;
	}

	void SoundClip::setVelocity(float velx, float vely, float velz)
	{
		mVelocity.x = velx;
		mVelocity.y = vely;
		mVelocity.z = velz;
		mLocalTransformDirty = true;
	}

	void SoundClip::setVelocity(const Ogre::Vector3& vel)
	{
		mVelocity = vel;
		mLocalTransformDirty = true;
	}

	void SoundClip::setVolume(float gain)
	{
		if (gain<0)
		{
			return;
		}

		mGain = gain;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_GAIN, mGain);
		}
	}

	void SoundClip::setMaxVolume(float maxGain)
	{
		if (maxGain<0 || maxGain>1)
			return;

		mMaxGain = maxGain;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_MAX_GAIN, mMaxGain);
		}
	}

	void SoundClip::setMinVolume(float minGain)
	{
		if (minGain<0 || minGain>1)
			return;

		mMinGain = minGain;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_MIN_GAIN, mMinGain);
		}
	}

	void SoundClip::setConeAngles(float insideAngle, float outsideAngle)
	{
		if (insideAngle<0 || insideAngle>360)
			return;
		if (outsideAngle<0 || outsideAngle>360)
			return;

		mInnerConeAngle = insideAngle;
		mOuterConeAngle = outsideAngle;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_CONE_INNER_ANGLE, mInnerConeAngle);
			alSourcef(mSource, AL_CONE_OUTER_ANGLE, mOuterConeAngle);
		}
	}

	void SoundClip::setOuterConeVolume(float gain)
	{
		if (gain<0 || gain>1)
			return;

		mOuterConeVolume = gain;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_CONE_OUTER_GAIN, mOuterConeVolume);
		}
	}

	void SoundClip::setMaxDistance(float maxDistance)
	{
		if (maxDistance<0)
			return;

		mMaxDistance = maxDistance;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance);
		}
	}

	void SoundClip::setRolloffFactor(float rolloffFactor)
	{
		if (rolloffFactor<0)
			return;

		mRolloffFactor = rolloffFactor;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_ROLLOFF_FACTOR, mRolloffFactor);
		}
	}

	void SoundClip::setReferenceDistance(float referenceDistance)
	{
		if (referenceDistance<0)
			return;

		mReferenceDistance = referenceDistance;

		if (mSource != AL_NONE)
		{
			alSourcef(mSource, AL_REFERENCE_DISTANCE, mReferenceDistance);
		}
	}

	void SoundClip::setPitch(float pitch)
	{
		mPitch = pitch;

		if(mSource != AL_NONE)
		{
			alSourcef(mSource, AL_PITCH, mPitch);
		}
	}

	void SoundClip::_initSource()
	{
		if (mSource != AL_NONE)
		{
			alSourcef (mSource, AL_GAIN, mGain);
			alSourcef (mSource, AL_MAX_GAIN, mMaxGain);
			alSourcef (mSource, AL_MIN_GAIN, mMinGain);
			alSourcef (mSource, AL_MAX_DISTANCE, mMaxDistance);	
			alSourcef (mSource, AL_ROLLOFF_FACTOR, mRolloffFactor);
			alSourcef (mSource, AL_REFERENCE_DISTANCE, mReferenceDistance);
			alSourcef (mSource, AL_CONE_OUTER_GAIN,	mOuterConeVolume);
			alSourcef (mSource, AL_CONE_INNER_ANGLE, mInnerConeAngle);
			alSourcef (mSource, AL_CONE_OUTER_ANGLE, mOuterConeAngle);
			alSource3f(mSource, AL_POSITION, mPosition.x, mPosition.y, mPosition.z);
			alSource3f(mSource, AL_DIRECTION, mDirection.x, mDirection.y, mDirection.z);
			alSource3f(mSource, AL_VELOCITY, mVelocity.x, mVelocity.y, mVelocity.z);
			alSourcef (mSource, AL_PITCH, mPitch);
			alSourcei (mSource, AL_SOURCE_RELATIVE, mSourceRelative);
			alSourcei (mSource, AL_LOOPING, mStream ? AL_FALSE : mLoop);
		}
	}

	Ogre::Real SoundClip::getVolume()
	{
		Ogre::Real vol=0.f;

		if (mSource != AL_NONE)
		{
			alGetSourcef(mSource, AL_GAIN, &vol);
		}

		return vol;
	}

	void SoundClip::startFade(bool dir, Ogre::Real fadeTime, FadeControl actionOnCompleted)
	{
		mFade = true;
	    mFadeInitVol = dir? 0.f : getVolume();
		mFadeEndVol = dir?1.f:0.f;
		mFadeTimer = 0.f;
		mFadeEndAction = actionOnCompleted;
		mFadeTime = fadeTime;
				
		if (mFadeEndVol == 1)
		{
			if (!isPlaying())
			{
				SoundManager::getSingleton().playSound(this->getName());
				//SoundManager::playSound(this->getName());
			}
		}
	}

	void SoundClip::_updateFade(Ogre::Real fTime)
	{
		if (mFade)
		{
			if ((mFadeTimer+=fTime) > mFadeTime)
			{
				setVolume(mFadeEndVol);
				mFade = false;
				switch (mFadeEndAction)
				{
				case FC_PAUSE:
					{
						SoundManager::getSingleton().pauseSound(getName());
						//SoundManager::getSingleton()->pauseSound(getName());
					} break;
				case FC_STOP:
					{
						SoundManager::getSingleton().stopSound(getName());
					} break;
				}
			}
			
			else 
			{
				Ogre::Real vol = (mFadeEndVol-mFadeInitVol)*(mFadeTimer/mFadeTime);
				setVolume(mFadeInitVol + vol);
			}
		}
	}

	void SoundClip::_markPlayPosition()
	{
		if (!mSeekable || (mSource==AL_NONE) || mStream)
			return;

		alSourcePause(mSource);
		alGetSourcef(mSource, AL_SEC_OFFSET, &mPlayPos);
	}

	void SoundClip::_recoverPlayPosition()
	{
		if (!mSeekable || (mSource==AL_NONE) || mStream)
			return;

		alSourcef(mSource, AL_SEC_OFFSET, mPlayPos);
		if (alGetError())
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound: Unable to set play position in function 'SoundClip::_recoverPlayPosition()'", Ogre::LML_CRITICAL);
		}
	}

	bool SoundClip::isPlaying()
	{
		if (mSource != AL_NONE)
		{
			ALenum state;
			alGetError();
			alGetSourcei(mSource, AL_SOURCE_STATE, &state);
			//return (state == AL_PLAYING);
			if (state = AL_PLAYING)
				return true;
			else
				return false;
		}

		return mPlay;
	}

	bool SoundClip::isPaused()
	{
		if (mSource != AL_NONE)
		{
			ALenum state;
			alGetError();
			//alGetSourcei(mSource, AL_SOURCE_STATE, &state);
			//return (state = AL_PAUSED);
			alGetSourcei(mSource, AL_SOURCE_STATE, &state);
			if (state = AL_PAUSED)
				return true;
			else
				return false;
		}

		return false;
	}

	bool SoundClip::isStopped()
	{
		if (mSource != AL_NONE)
		{
			ALenum state;
			alGetError();
			alGetSourcei(mSource, AL_SOURCE_STATE, &state);
			//return (state = AL_STOPPED);
			if (state = AL_STOPPED)
				return true;
			else
				return false;
		}

		return true;
	}

	void SoundClip::setPlayPosition(Ogre::Real seconds)
	{
		if (!mSeekable || seconds <0.f)
			return;

		if (seconds > mPlayTime)
		{
			do {seconds-=mPlayTime;}
			while (seconds >mPlayTime);
		}

		if (mSource != AL_NONE)
		{
			alGetError();
			alSourcef(mSource, AL_SEC_OFFSET, seconds);
			if (alGetError())
			{
				Ogre::LogManager::getSingleton().logMessage("UniverseSound: Error setting the play positiion in 'SoundClip::setPlayPosition'", Ogre::LML_CRITICAL);
			}
			mPlayPosChanged = false;
		}

		else
		{
			mPlayPosChanged = true;
			mPlayPos = seconds;
		}
	}

	void SoundClip::setRelativeToListener(bool relative)
	{
		mSourceRelative = relative;

		if (mSource != AL_NONE)
		{
			alSourcei(mSource, AL_SOURCE_RELATIVE, mSourceRelative);
		}
	}

	void SoundClip::update(Ogre::Real fTime)
	{
		if (mLocalTransformDirty)
		{
			if (mParentNode)
			{
				mPosition = mParentNode->_getDerivedPosition();
				mDirection = -mParentNode->_getDerivedOrientation().zAxis();
			}

			if (mSource != AL_NONE)
			{
				alSource3f(mSource, AL_POSITION, mPosition.x, mPosition.y, mPosition.z);
				alSource3f(mSource, AL_DIRECTION, mDirection.x, mDirection.y, mDirection.z);
			}

			mLocalTransformDirty = false;
		}

		_updateFade(fTime);
	}

	const Ogre::String& SoundClip::getMovableType(void) const
	{
		static Ogre::String typeName = "SoundClip";
		return typeName;
	}

	const Ogre::AxisAlignedBox& SoundClip::getBoundingBox(void) const
	{
		static Ogre::AxisAlignedBox aab;
		return aab;
	}

	Ogre::Real SoundClip::getBoundingRadius(void) const
	{
		return 0;
	}

	void SoundClip::_updateRenderQueue(Ogre::RenderQueue *queue)
	{
		return;
	}

	void SoundClip::_notifyAttached(Ogre::Node *node, bool isTagPoint)
	{
		Ogre::MovableObject::_notifyAttached(node, isTagPoint);

		if (mParentNode)
		{
			mPosition = mParentNode->_getDerivedPosition();
			mDirection = -mParentNode->_getDerivedOrientation().zAxis();
		}

		if (mSource != AL_NONE)
		{
			alSource3f(mSource, AL_POSITION, mPosition.x, mPosition.y, mPosition.z);
			alSource3f(mSource, AL_DIRECTION, mDirection.x, mDirection.y, mDirection.z);
		}

		return;
	}

	void SoundClip::_notifyMoved()
	{
		mLocalTransformDirty = true;
	}

	void SoundClip::visitRenderables(Ogre::Renderable::Visitor *visitor, bool debugRenderables)
	{
		return;
	}

	ALuint SoundClip::getSource()
	{
		return mSource;
	}

	Ogre::Vector3& SoundClip::getPosition()
	{
		return mPosition;
	}

	bool SoundClip::isRelativeToListener()
	{
		return mSourceRelative;
	}

	void SoundClip::loop(bool loop)
	{
		mLoop = loop;
	}

	void SoundClip::open(const Ogre::String &fileName, ALuint &buffer)
	{
		return;
	}
}